/*
    Project:        Home Power Control
    File:           HPCDHT.cpp
    Author:         christian@karunamusic.at
    Date:           02.2015
    Description:    Sensor to measure humidity and temperature with the DHT22.
                    Code partly from Adafruit
                    https://learn.adafruit.com/dht-humidity-sensing-on-raspberry-pi-with-gdocs-logging/software-install-updated
 */

#include <wiringPi.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <sched.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <unistd.h>
#include <Logger/HPCLogger.h>
#include "HPCDHT.h"

// Number of bit pulses to expect from the DHT.  Note that this is 41 because
// the first pulse is a constant 50 microsecond pulse, with 40 pulses to represent
// the data afterwards.
#define DHT_PULSES 41
#define DHT_MAXCOUNT 32000

#define MAXTIMINGS 85

HPCDHT::HPCDHT(TSensorType type, const QString& name, quint32 id, quint32 pin )
    : HPCSensor( type,name,id,pin )
{
    m_Hum.V() = m_Temp.V() = 0.0f;
}

HPCDHT::~HPCDHT()
{
}

void HPCDHT::Measure()
{
    if ( !querySensor() )
    {
        sleep_milliseconds(200);
    }
}

bool HPCDHT::querySensor()
{
    // Store the count that each DHT bit pulse is low and high.
    // Make sure array is initialized to start at zero.
    unsigned int pulseCounts[DHT_PULSES*2] = {0};

    // Set pin to output.
    pinMode(m_Pin, OUTPUT);

    // Bump up process priority and change scheduler to try to make process more 'real time'.
    set_max_priority();

    // Set pin high for ~500 milliseconds.
    digitalWrite(m_Pin, HIGH);
    sleep_milliseconds(500);

    // The next calls are timing critical and care should be taken
    // to ensure no unnecssary work is done below.

    // Set pin low for ~20 milliseconds.
    digitalWrite(m_Pin, LOW);
    busy_wait_milliseconds(20);

    // Set pin to output.
    pinMode(m_Pin, INPUT);

    // Need a very short delay before reading pins or else value is sometimes still low.
    for (volatile int i = 0; i < 50; ++i) {}

    // Wait for DHT to pull pin low.
    uint32_t count = 0;
    while ( digitalRead(m_Pin) )
    {
        if (++count >= DHT_MAXCOUNT)
        {
            // Timeout waiting for response.
            set_default_priority();
            LOG( debug, "Timeout (1)!" );
            return false;
        }
    }

    // Record pulse widths for the expected result bits.
    for (int i=0; i < DHT_PULSES*2; i+=2)
    {
        // Count how long pin is low and store in pulseCounts[i]
        while (!digitalRead(m_Pin))
        {
            if (++pulseCounts[i] >= DHT_MAXCOUNT)
            {
                // Timeout waiting for response.
                set_default_priority();
                LOG( debug, "Timeout (2)!" );
                return false;
            }
        }
        // Count how long pin is high and store in pulseCounts[i+1]
        while (digitalRead(m_Pin))
        {
            if (++pulseCounts[i+1] >= DHT_MAXCOUNT)
            {
                // Timeout waiting for response.
                set_default_priority();
                LOG( debug, "Timeout (3)!" );
                return false;
            }
        }
    }

    // Done with timing critical code, now interpret the results.

    // Drop back to normal priority.
    set_default_priority();

    // Compute the average low pulse width to use as a 50 microsecond reference threshold.
    // Ignore the first two readings because they are a constant 80 microsecond pulse.
    uint32_t threshold = 0;
    for (int i=2; i < DHT_PULSES*2; i+=2)
    {
        threshold += pulseCounts[i];
    }
    threshold /= DHT_PULSES-1;

    // Interpret each high pulse as a 0 or 1 by comparing it to the 50us reference.
    // If the count is less than 50us it must be a ~28us 0 pulse, and if it's higher
    // then it must be a ~70us 1 pulse.
    uint8_t data[5] = {0};
    for (int i=3; i < DHT_PULSES*2; i+=2)
    {
        int index = (i-3)/16;
        data[index] <<= 1;
        if (pulseCounts[i] >= threshold)
        {
            // One bit for long pulse.
            data[index] |= 1;
        }
        // Else zero bit for short pulse.
    }

    // Useful debug info:
    //printf("Data: 0x%x 0x%x 0x%x 0x%x 0x%x\n", data[0], data[1], data[2], data[3], data[4]);

    // Verify checksum of received data.
    if (data[4] == ((data[0] + data[1] + data[2] + data[3]) & 0xFF))
    {
        /*
        if (type == DHT11)
        {
            // Get humidity and temp for DHT11 sensor.
            m_Value = (float)data[0];
            m_AddVal = (float)data[2];
        }
        else if (type == DHT22)
        {
        */
            // Calculate humidity and temp for DHT22 sensor.
            m_Hum.V() = (data[0] * 256 + data[1]) / 10.0f;
            m_Temp.V() = ((data[2] & 0x7F) * 256 + data[3]) / 10.0f;
            if (data[2] & 0x80)
            {
                float f = m_Temp.V().toFloat();
                f *= -1.0f;
                m_Temp.V() = f;
            }

        /*
        }
        */
        return true;
    }
    else
    {
        LOG( debug, "Invalid checksum!" );
    }

    return false;
}

void HPCDHT::sleep_milliseconds( uint32_t millis )
{
    struct timespec sleep;
    sleep.tv_sec = millis / 1000;
    sleep.tv_nsec = (millis % 1000) * 1000000L;
    while (clock_nanosleep(CLOCK_MONOTONIC, 0, &sleep, &sleep) && errno == EINTR);
}

void HPCDHT::busy_wait_milliseconds( uint32_t millis )
{
    // Set delay time period.
    struct timeval deltatime;
    deltatime.tv_sec = millis / 1000;
    deltatime.tv_usec = (millis % 1000) * 1000;
    struct timeval walltime;
    // Get current time and add delay to find end time.
    gettimeofday(&walltime, NULL);
    struct timeval endtime;
    timeradd(&walltime, &deltatime, &endtime);
    // Tight loop to waste time (and CPU) until enough time as elapsed.
    while (timercmp(&walltime, &endtime, <))
    {
        gettimeofday(&walltime, NULL);
    }
}

void HPCDHT::set_max_priority()
{
    struct sched_param sched;
    memset(&sched, 0, sizeof(sched));
    // Use FIFO scheduler with highest priority for the lowest chance of the kernel context switching.
    sched.sched_priority = sched_get_priority_max(SCHED_FIFO);
    sched_setscheduler(0, SCHED_FIFO, &sched);
}

void HPCDHT::set_default_priority()
{
    struct sched_param sched;
    memset(&sched, 0, sizeof(sched));
    // Go back to default scheduler with default 0 priority.
    sched.sched_priority = 0;
    sched_setscheduler(0, SCHED_OTHER, &sched);
}

/**
 *   HPCHumidity22
 */
void HPCHumidity22::Measure()
{
    HPCDHT::Measure();
    m_Value = m_Hum;
}

/**
 *   HPCTemperature22
 */
void HPCTemperature22::Measure()
{
    HPCDHT::Measure();
    m_Value = m_Temp;
}
